package com.reebake.ideal.user.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.reebake.ideal.user.core.UserStatusConstants;
import com.reebake.ideal.user.entity.UserCredential;
import com.reebake.ideal.user.entity.UserInfo;
import com.reebake.ideal.user.entity.UserThirdParty;
import com.reebake.ideal.user.exception.*;
import com.reebake.ideal.user.mapper.UserCredentialMapper;
import com.reebake.ideal.user.mapper.UserInfoMapper;
import com.reebake.ideal.user.mapper.UserThirdPartyMapper;
import com.reebake.ideal.user.service.UserAuthService;
import com.reebake.ideal.verify.code.core.VerifyCodeService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserAuthServiceImpl implements UserAuthService {

    private final UserCredentialMapper userCredentialMapper;
    private final UserInfoMapper userInfoMapper;
    private final UserThirdPartyMapper userThirdPartyMapper;
    private final PasswordEncoder passwordEncoder;
    private final VerifyCodeService verifyCodeService;

    @Override
    public UserCredential queryByUserId(String userId) {
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getUserId, userId);

        return userCredentialMapper.selectOne(queryWrapper);
    }

    @Override
    public UserCredential queryByUsername(String username) {
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getUsername, username);

        return userCredentialMapper.selectOne(queryWrapper);
    }

    @Override
    public UserCredential queryByPhoneNumber(String phoneNumber) {
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getPhoneNumber, phoneNumber);

        return userCredentialMapper.selectOne(queryWrapper);
    }

    @Override
    public UserCredential queryByEmail(String email) {
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getEmail, email);

        return userCredentialMapper.selectOne(queryWrapper);
    }

    @Override
    public UserCredential queryByThirdParty(String uuid, String source) {
        LambdaQueryWrapper<UserThirdParty> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserThirdParty::getUuid, uuid).eq(UserThirdParty::getSource, source);

        UserThirdParty userThirdParty = userThirdPartyMapper.selectOne(queryWrapper);
        if(ObjUtil.isNull(userThirdParty)) {
            return null;
        }

        UserCredential userCredential = new UserCredential();
        userCredential.setUsername(uuid + ":" + source);
        userCredential.setUserId(userThirdParty.getUserId());

        userCredential.setAccountEnabled(true);
        userCredential.setAccountNonLocked(true);
        userCredential.setAccountNonExpired(true);
        userCredential.setCredentialsNonExpired(true);

        return userCredential;
    }

    @Override
    public boolean existsByUsername(String username) {
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getUsername, username);

        return userCredentialMapper.exists(queryWrapper);
    }

    @Override
    public boolean existsByPhoneNumber(String phoneNumber) {
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getPhoneNumber, phoneNumber);

        return userCredentialMapper.exists(queryWrapper);
    }

    @Override
    public boolean existsByEmail(String email) {
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getEmail, email);

        return userCredentialMapper.exists(queryWrapper);
    }

    @Override
    public boolean existsByThirdParty(String uuid, String source) {
        LambdaQueryWrapper<UserThirdParty> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserThirdParty::getUuid, uuid).eq(UserThirdParty::getSource, source);

        return userThirdPartyMapper.exists(queryWrapper);
    }

    @Override
    public String registerByUsername(String username, String password) {
        Assert.notBlank(username, "username must not be null.");
        Assert.notBlank(password, "password must not be null.");
        if(this.existsByUsername(username)) {
            throw new UsernameExistsException();
        }
        UserCredential userCredential = new UserCredential();
        userCredential.setUsername(username);
        String encodedPassword = passwordEncoder.encode(password);
        userCredential.setPassword(encodedPassword);

        return register(null, userCredential);
    }

    @Override
    public String registerByPhoneNumber(String phoneNumber, String verifyCode) {
        Assert.notBlank(phoneNumber, "phoneNumber must not be null.");
        Assert.notBlank(verifyCode, "verifyCode must not be null.");
        if(this.existsByPhoneNumber(phoneNumber)) {
            throw new UsernameExistsException();
        }
        Boolean result = this.verifyCodeService.check(phoneNumber, verifyCode);
        if(BooleanUtil.isFalse(result)) {
            throw new VerifyCodeFailureException();
        }
        UserCredential userCredential = new UserCredential();
        userCredential.setPhoneNumber(phoneNumber);

        return register(null, userCredential);
    }

    @Override
    public String registerByEmail(String email, String verifyCode) {
        Assert.notBlank(email, "email must not be null.");
        Assert.notBlank(verifyCode, "verifyCode must not be null.");
        if(this.existsByEmail(email)) {
            throw new UsernameExistsException();
        }
        Boolean result = this.verifyCodeService.check(email, verifyCode);
        if(BooleanUtil.isFalse(result)) {
            throw new VerifyCodeFailureException();
        }
        UserCredential userCredential = new UserCredential();
        userCredential.setEmail(email);

        return register(null, userCredential);
    }

    @Override
    @Transactional
    public String register(UserInfo userInfo, UserCredential userCredential) {
        if(ObjUtil.isNull(userInfo)) {
            userInfo = new UserInfo();
        }
        if(StrUtil.isBlank(userInfo.getAvatar())) {
            userInfo.setAvatar(StrUtil.EMPTY);
        }
        if(StrUtil.isBlank(userInfo.getNickname())) {
            userInfo.setNickname(RandomUtil.randomString(8));
        }
        if(StrUtil.isBlank(userInfo.getUserStatus())) {
            userInfo.setUserStatus(UserStatusConstants.NORMAL);
        }
        userInfoMapper.insert(userInfo);

        userCredential.setUserId(userInfo.getUserId());
        userCredentialMapper.insert(userCredential);

        return userInfo.getUserId();
    }

    private void saveOrUpdateCredential(UserCredential userCredential) {
        Assert.notBlank(userCredential.getUserId());
        UserCredential uc = queryByUserId(userCredential.getUserId());
        if(ObjUtil.isNotNull(uc)) {
            userCredential.setUserCredentialId(uc.getUserCredentialId());
            userCredentialMapper.updateById(userCredential);
        }else {
            userCredentialMapper.insert(userCredential);
        }
    }

    @Override
    public void saveCredentialByUsername(String userId, String username, String password) {
        Assert.notBlank(userId, "userId must not be null.");
        Assert.notBlank(username, "username must not be null.");
        Assert.notBlank(password, "password must not be null.");

        UserCredential userCredential = new UserCredential();
        userCredential.setUserId(userId);
        userCredential.setUsername(username);
        String encodedPassword = passwordEncoder.encode(password);
        userCredential.setPassword(encodedPassword);

        saveOrUpdateCredential(userCredential);
    }

    @Override
    public void saveCredentialByPhoneNumber(String userId, String phoneNumber, String verifyCode) {
        Assert.notBlank(userId, "userId must not be null.");
        Assert.notBlank(phoneNumber, "phoneNumber must not be null.");
        Assert.notBlank(verifyCode, "verifyCode must not be null.");

        Boolean result = this.verifyCodeService.check(phoneNumber, verifyCode);
        if(BooleanUtil.isFalse(result)) {
            throw new VerifyCodeFailureException();
        }
        UserCredential userCredential = new UserCredential();
        userCredential.setUserId(userId);
        userCredential.setPhoneNumber(phoneNumber);

        saveOrUpdateCredential(userCredential);
    }

    @Override
    public void saveCredentialByEmail(String userId, String email, String verifyCode) {
        Assert.notBlank(userId, "userId must not be null.");
        Assert.notBlank(email, "email must not be null.");
        Assert.notBlank(verifyCode, "verifyCode must not be null.");

        Boolean result = this.verifyCodeService.check(email, verifyCode);
        if(BooleanUtil.isFalse(result)) {
            throw new VerifyCodeFailureException();
        }
        UserCredential userCredential = new UserCredential();
        userCredential.setUserId(userId);
        userCredential.setEmail(email);

        saveOrUpdateCredential(userCredential);
    }

	@Override
	public void resetPassword(String username, String verifyCode, String password) {
		UserCredential userCredential = queryByUsername(username);
		if(ObjUtil.isNull(userCredential)) {
			throw new UsernameNotFoundException();
		}
		Boolean result = this.verifyCodeService.check(username, verifyCode);
        if(BooleanUtil.isFalse(result)) {
            throw new VerifyCodeFailureException();
        }
		String encodedPassword = passwordEncoder.encode(password);
		UserCredential updateEntity = new UserCredential();
		updateEntity.setPassword(encodedPassword);
		updateEntity.setUserCredentialId(userCredential.getUserCredentialId());
		
		userCredentialMapper.updateById(updateEntity);
	}

    @Override
    public void bindThirdPartyUser(String userId, String uuid, String source) {
        LambdaUpdateWrapper<UserThirdParty> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(UserThirdParty::getUserId, userId)
                .eq(UserThirdParty::getUuid, uuid)
                .eq(UserThirdParty::getSource, source);

        userThirdPartyMapper.update(updateWrapper);
    }

    @Override
    public void changeUsername(String userId, String password, String newUsername) {
        UserCredential userCredential = queryByUserId(userId);
        if(!passwordEncoder.matches(password, userCredential.getPassword())){
            throw new PasswordMatchFailureException();
        }
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getUsername, newUsername)
                .ne(UserCredential::getUserId, userId);
        if(userCredentialMapper.exists(queryWrapper)) {
            throw new UsernameExistsException();
        }

        LambdaUpdateWrapper<UserCredential> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(UserCredential::getUsername, newUsername)
                .eq(UserCredential::getUserId, userId);
        userCredentialMapper.update(updateWrapper);
    }

    @Override
    public void changePassword(String userId, String password, String newPassword) {
        UserCredential userCredential = queryByUserId(userId);
        if(!passwordEncoder.matches(password, userCredential.getPassword())){
            throw new PasswordMatchFailureException();
        }

        LambdaUpdateWrapper<UserCredential> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(UserCredential::getPassword, passwordEncoder.encode(newPassword))
                .eq(UserCredential::getUserId, userId);
        userCredentialMapper.update(updateWrapper);
    }

    @Override
    public void changePhoneNumber(String userId, String verifyCode, String newPhoneNumber, String newVerifyCode) {
        UserCredential userCredential = queryByUserId(userId);
        Boolean result = this.verifyCodeService.check(userCredential.getPhoneNumber(), verifyCode);
        if(BooleanUtil.isFalse(result)){
            throw new VerifyCodeFailureException();
        }
        result = this.verifyCodeService.check(newPhoneNumber, newVerifyCode);
        if(BooleanUtil.isFalse(result)){
            throw new VerifyCodeFailureException();
        }
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getPhoneNumber, newPhoneNumber)
                .ne(UserCredential::getUserId, userId);
        if(userCredentialMapper.exists(queryWrapper)) {
            throw new PhonenumberExistsException();
        }

        LambdaUpdateWrapper<UserCredential> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(UserCredential::getPhoneNumber, newPhoneNumber)
                .eq(UserCredential::getUserId, userId);
        userCredentialMapper.update(updateWrapper);
    }

    @Override
    public void deleteUser(String userId) {
        LambdaQueryWrapper<UserCredential> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCredential::getUserId, userId);
        userCredentialMapper.delete(queryWrapper);
    }

    @Override
    public void lockByUserId(String userId) {
        LambdaUpdateWrapper<UserCredential> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(UserCredential::getAccountNonLocked, false)
                .eq(UserCredential::getUserId, userId);
        userCredentialMapper.update(updateWrapper);
    }
}
